--
-- Distribution Id Generate Script
-- User: allen.liu
-- Date: 2019/5/5
-- Time: 14:35
-- To change this template use File | Settings | File Templates.
--

-- Redis Structure
--      current_value - current value
--      max_value_available - max value available
-- Statistics Structure
--      current milliseconds: current value

-- Get statistics hash value
-- @param fieldName - raw fieldName
-- @return statistics structure hash fieldName
local function internalGetStatisticKey(fieldName)
    return fieldName .. ':statistic';
end

-- Get n minutes statistics value
-- if get no value, default 200 per minutes
-- @param fieldName - raw fieldName
-- @param currentMilliSeconds - current milliseconds
-- @param lastMinutes - interval
-- @return n minutes statistics value
local function internalGetStatisticValue(fieldName, currentMilliSeconds, lastMinutes)
    local lastMinutesValue = redis.pcall('HGET', internalGetStatisticKey(fieldName), currentMilliSeconds - lastMinutes * 60 * 1000);
    if (type(lastMinutesValue) == 'boolean' or lastMinutesValue == nil) then
        return 200 * lastMinutes;
    end
    return lastMinutesValue;
end


-- Refresh max available value
-- if max available value - current value more than 15 minutes requirements, then expand to 1 hour
-- if current value doesn't exists, may be exception(may be the redis has retart), then update current value to max available value, and use algorithm to refresh max available value
-- @param fieldName Key
-- @param currentValue current value
-- @param currentMilliSeconds current milliseconds
-- @return refreshed value after refresh
local function internalRefreshMaxValueAvailable(fieldName, step, currentMilliSeconds)
    -- get max available value
    local maxValueAvailable = redis.pcall('HGET', fieldName, 'max_value_available');
    if (type(maxValueAvailable) == 'boolean' or maxValueAvailable == nil) then
        return nil;
    end

    -- get current value, if doesn't exists ,init to max available value
    local currentValue = redis.pcall('HGET', fieldName, 'current_value');
    if (type(currentValue) == 'boolean' or currentValue == nil) then
        currentValue = maxValueAvailable;
        redis.pcall('HSET', fieldName, 'current_value', currentValue);
    end

    -- get 5 minutes recent statistics value
    local lastFiveMinutesSts = internalGetStatisticValue(fieldName, currentMilliSeconds, 5);

    -- get 30 minutes recent statistics value
    local lastThirtyMinutesSts = internalGetStatisticValue(fieldName, currentMilliSeconds, 30);

    -- init step if nil
    if (step == nil) then
        step = 1;
    end

    -- if current value more than 15 minutes requirements, then expand to 1 hour
    if (maxValueAvailable - currentValue < lastFiveMinutesSts * 3 * step) then
        maxValueAvailable = redis.pcall('HINCRBY', fieldName, 'max_value_available', lastThirtyMinutesSts * step * 2);
    end

    return maxValueAvailable;
end

-- get distributeion ID
-- @param fieldName Key
-- @param step step
-- @param currentMilliSeconds
-- @param maxValueAvailableFromDB
-- @return success - currentValue,maxValueAvailable,refreshedMaxValueAvailable failed - nil
local function getDistributionId(fieldName, step, currentMilliSeconds, maxValueAvailableFromDB)
    -- max available value from redis
    local maxValueAvailable = redis.pcall('HGET', fieldName, 'max_value_available');
    if (type(maxValueAvailable) == 'boolean' or maxValueAvailable == nil) then
        if (maxValueAvailableFromDB == nil) then
            -- nil
            return nil;
        else
            -- init with db value
            maxValueAvailable = maxValueAvailableFromDB;
            redis.pcall('HSET', fieldName, 'max_value_available', maxValueAvailable);
        end
    end

    -- refresh
    local maxValueAvailableRefresh = internalRefreshMaxValueAvailable(fieldName, step, currentMilliSeconds);


    -- get current value
    local currentValue = redis.pcall('HINCRBY', fieldName, 'current_value', step);
    if (type(currentValue) == 'boolean' or currentValue == nil) then
        return nil;
    end

    -- validate
    if (type(maxValueAvailableRefresh) == 'boolean' or maxValueAvailableRefresh == nil) then
        return nil;
    elseif tonumber(maxValueAvailableRefresh) < tonumber(currentValue) then
        return nil;
    end

    -- statistics
    redis.pcall('HSET', internalGetStatisticKey(fieldName), currentMilliSeconds, currentValue);

    return currentValue .. ',' .. maxValueAvailable .. ',' .. maxValueAvailableRefresh;
end


-- main
local fieldName = KEYS[1]
local method = ARGV[1]
print('fieldName: ' .. fieldName .. ', method: ' .. method);
if method == 'getDistributionId' then
    return getDistributionId(fieldName, tonumber(ARGV[2]), tonumber(ARGV[3]), tonumber(ARGV[4]));
else
    --ignore
    print('Ignore: nil');
    return nil;
end